package com.liu.yupao.service.impl;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.liu.yupao.common.ErrorCode;
import com.liu.yupao.exception.BusinessException;
import com.liu.yupao.model.dto.user.UserUpdateRequest;
import com.liu.yupao.model.entity.User;
import com.liu.yupao.model.enums.UserRoleEnum;
import com.liu.yupao.model.vo.UserVO;
import com.liu.yupao.service.UserService;
import com.liu.yupao.mapper.UserMapper;
import com.liu.yupao.utils.AlgorithmUtils;
import com.liu.yupao.utils.CopyUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.DigestUtils;
import org.springframework.web.bind.annotation.RequestParam;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static com.liu.yupao.contant.UserConstant.USER_LOGIN_STATE;

/**
 * 用户服务实现类

 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService {

    @Resource
    private UserMapper userMapper;

    @Resource
    private RedisTemplate<String,Object> redisTemplate;

    /**
     * 盐值，混淆密码
     */
    private static final String SALT = "salt";

    /**
     * 用户注册
     *
     * @param userAccount   用户账户
     * @param userPassword  用户密码
     * @param checkPassword 校验密码
     * @return 新用户 id
     */
    @Override
    public long userRegister(String userAccount, String userPassword, String checkPassword) {
        // 1. 校验
        if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
        }
        if (userAccount.length() < 4) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号过短");
        }
        if (userPassword.length() < 8 || checkPassword.length() < 8) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码过短");
        }
        // 账户不能包含特殊字符
        String validPattern = "[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~！@#￥%……&*（）——+|{}【】‘；：”“’。，、？]";
        Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
        if (matcher.find()) {
            throw new BusinessException (ErrorCode.PARAMS_ERROR,"账户名非法");
        }
        // 密码和校验密码相同
        if (!userPassword.equals(checkPassword)) {
            throw new BusinessException (ErrorCode.PARAMS_ERROR,"两次输入密码不一致");
        }
        // 账户不能重复
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userAccount", userAccount);
        long count = userMapper.selectCount(queryWrapper);
        if (count > 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号重复");
        }
        // 2. 加密
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
        // 3. 插入数据
        User user = new User();
        user.setUserAccount(userAccount);
        user.setUserPassword(encryptPassword);
        boolean saveResult = this.save(user);
        if (!saveResult) {
            throw new BusinessException (ErrorCode.SYSTEM_ERROR,"数据库插入失败");
        }
        return user.getId();
    }

    /**
     * 用户登录
     *
     * @param userAccount  用户账户
     * @param userPassword 用户密码
     * @param request
     * @return 脱敏后的用户信息
     */
    @Override
    public UserVO userLogin(String userAccount, String userPassword, HttpServletRequest request) {
        // 1. 校验
        if (StringUtils.isAnyBlank(userAccount, userPassword)) {
            throw new BusinessException (ErrorCode.PARAMS_ERROR);
        }
        if (userAccount.length() < 4) {
            throw new BusinessException (ErrorCode.PARAMS_ERROR,"账号账户名小于4位");
        }
        if (userPassword.length() < 8) {
            throw new BusinessException (ErrorCode.PARAMS_ERROR,"账号密码小于8位");
        }
        // 账户不能包含特殊字符
        String validPattern = "[`~!@#$%^&*()+=|{}':;',\\\\[\\\\].<>/?~！@#￥%……&*（）——+|{}【】‘；：”“’。，、？]";
        Matcher matcher = Pattern.compile(validPattern).matcher(userAccount);
        if (matcher.find()) {
            throw new BusinessException (ErrorCode.PARAMS_ERROR,"账户名非法");
        }
        // 2. 加密
        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
        // 查询用户是否存在
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userAccount", userAccount);
        queryWrapper.eq("userPassword", encryptPassword);
        User user = userMapper.selectOne(queryWrapper);
        // 用户不存在
        if (user == null) {
            log.info("user login failed, userAccount cannot match userPassword");
            throw new BusinessException (ErrorCode.PARAMS_ERROR,"该用户不存在，请检查账号信息");
        }
        //3.记录登录态
        request.getSession ().setAttribute (USER_LOGIN_STATE,user);
        //4.返回脱敏数据
        return CopyUtils.copy (user, UserVO.class);
    }


    /**
     * 用户注销
     *
     * @param request
     */
    @Override
    public int userLogout(HttpServletRequest request) {
        // 移除登录态
        request.getSession().removeAttribute(USER_LOGIN_STATE);
        return 1;
    }

    /**
     * 根据标签内容查找用户
     */
    @Override
    public List<UserVO> searchUsersByTags(@RequestParam List<String> tagNames){
        if (CollectionUtils.isEmpty (tagNames)){
            throw new BusinessException (ErrorCode.PARAMS_ERROR);
        }
        //内存实现
        //先搜索出所有用户信息
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<> ();
        List<User> userList = userMapper.selectList (userQueryWrapper);
        //序列化
        Gson gson = new Gson ();
        //遍历数据
        return userList.stream ().filter (user -> {
            String tag = user.getTags ();
            //json解析
            List<String> tagList=gson.fromJson (tag,new TypeToken<List<String>> (){}.getType ());
            //java8新特性 optional 减少写 if else
            tagList=Optional.ofNullable (tagList).orElse (new ArrayList<> ());
//            //包含一个标签
//            for (String tagName : tagNames) {
//                if (tagList.contains (tagName)){
//                    return true;
//                }
//            }
            //包含所有标签
             return new HashSet<> (tagList).containsAll (tagNames);
                }).map (UserVO::toUserVO)
                .collect(Collectors.toList());
    }

    /**
     * 判断是都是管理员
     * @param request
     * @return
     */
    @Override
    public Boolean isAdmin(HttpServletRequest request) {
        User loginUser = (User)request.getSession ().getAttribute (USER_LOGIN_STATE);
        return loginUser!=null&& UserRoleEnum.ADMIN.getValue ().equals (loginUser.getUserRole ());
    }


    @Override
    public int updateUser(UserUpdateRequest userUpdateRequest,HttpServletRequest request) {
        //1.校验参数
        if (userUpdateRequest == null || userUpdateRequest.getId () == null) {
            throw new BusinessException (ErrorCode.PARAMS_ERROR);
        }
        //如果没有需要修改的信息，报错
        String username = userUpdateRequest.getUsername ();
        String profile = userUpdateRequest.getProfile ();
        String userAvatar = userUpdateRequest.getUserAvatar ();
        String gender = JSONUtil.toJsonStr (userUpdateRequest.getGender ());
        String phone = userUpdateRequest.getPhone ();
        String email = userUpdateRequest.getEmail ();
        if (StringUtils.isAllEmpty (username,profile,userAvatar,gender,phone,email)){
            throw new BusinessException (ErrorCode.PARAMS_ERROR);
        }

        //2.校验权限
        User loginUser = getLoginUser (request);
        //如果是管理员，允许修改任何信息
        if (isAdmin (request)) {
            User oldUser = userMapper.selectById (userUpdateRequest.getId ());
            if (oldUser == null) {
                throw new BusinessException (ErrorCode.NO_AUTH);
            }
            //有则更新
            User copy = CopyUtils.copy (userUpdateRequest, User.class);
            return userMapper.updateById (copy);
        }
        //自己可以修改
        if (!loginUser.getId ().equals (userUpdateRequest.getId ())) {
            throw new BusinessException (ErrorCode.NO_AUTH);
        }
        User oldUser = userMapper.selectById (userUpdateRequest.getId ());
        if (oldUser==null){
            throw new BusinessException (ErrorCode.NO_AUTH);
        }
        //3.触发更新
        User copy = CopyUtils.copy (userUpdateRequest, User.class);
        return userMapper.updateById (copy);


    }

    /**
     * 获取登录用户
     * @param request
     * @return
     */
    @Override
    public User getLoginUser(HttpServletRequest request) {
        User loginUser = (User)request.getSession ().getAttribute (USER_LOGIN_STATE);
        if (loginUser==null){
            throw new BusinessException (ErrorCode.NOT_LOGIN);
        }
        return loginUser;
    }

    /**
     * 推荐用户展示
     * @param current 当前页
     * @param pageSize 页面大小
     * @return page
     */
    @Override
    public Page<User> recommendUsers(long current, long pageSize,HttpServletRequest request) {
        //获取当前用户
        User loginUser = getLoginUser (request);
        //先判断缓存内是否有信息
        ValueOperations<String, Object> opsForValue = redisTemplate.opsForValue ();
        String redisKey=String.format ("yuPao:user:recommend:%s",loginUser.getId ());
        Page<User> userPage = (Page<User>) opsForValue.get (redisKey);
        if (userPage !=null){
            return userPage;
        }
        //没有则从数据中查询
        //分页显示用户 current,pageSize
        Page<User> page = this.page (new Page<> (current, pageSize));
        //存入redis缓存中
        try {
            //设置过期时间 到次日凌晨
            // 获取当前时间
            LocalDateTime now = LocalDateTime.now();
            // 计算次日凌晨时间（今天的24:00:00）
            LocalDateTime nextMidnight = now.plusDays(1).withHour(0).withMinute(0)
                    .withSecond(0).withNano(0);
            // 计算两个时间之间的差值
            Duration duration = Duration.between(now, nextMidnight);
            // 转换为秒数
            long secondsToMidnight = duration.getSeconds();
            opsForValue.set (redisKey,page, secondsToMidnight , TimeUnit.SECONDS);
        }catch (Exception e){
            //这里只需要日志记录一下即可，不需要报错，因为还可以从数据库中获取
            log.error ("redis set key error",e);
        }
        return page;
    }

    /**
     * 获取最匹配的用户信息
     * @param num 条数
     * @param request 当前用户信息
     */
    @Override
    public List<UserVO> match (Long num, HttpServletRequest request) {
        //添加查询条件
        QueryWrapper<User> queryWrapper = new QueryWrapper<> ();
        //只查询tags标签不为空的数据
        queryWrapper.isNotNull ("tags");
        //只查询需要的字段 id tags
        queryWrapper.select ("id","tags");
        //获取当前用户信息
        User loginUser = this.getLoginUser (request);
        //标签
        String tags = loginUser.getTags ();
        Gson gson = new Gson ();
        List<String> tagsList = gson.fromJson (tags, new TypeToken<List<String>> () {
        }.getType ());
        //存储用户的索引和匹配得分
        HashMap<Integer, Integer> indexMatchMap = new HashMap<> ();
        //获取所有用户
        List<User> userList = this.list (queryWrapper);
        for (int i = 0; i < userList.size (); i++) {
            //无标签
            String tags1 = userList.get (i).getTags ();
            //剔除自己
            if (loginUser.getId ().equals (userList.get (i).getId ())){
                continue;
            }
            //匹配分数
            List<String> newTagsList = gson.fromJson (tags1, new TypeToken<List<String>> () {
            }.getType ());
            //记录用户匹配分数
            int s = AlgorithmUtils.minDistance (tagsList, newTagsList);
            indexMatchMap.put (i, s);
        }
        // 核心：将Map的键值对转换为List，按value（分数）从小到大排序
        List<Map.Entry<Integer, Integer>> sortedEntries = new ArrayList<>(indexMatchMap.entrySet());
        List<Map.Entry<Integer, Integer>> collect = sortedEntries.stream ()
                .sorted (Comparator.comparingInt (Map.Entry::getValue))
                .limit (num).collect (Collectors.toList ());
        //设置用户完整信息
        return collect.stream ()
                .map (item->this.getById (userList.get (item.getKey ()).getId ()))
                .map (UserVO::toUserVO)
                .limit (num).collect (Collectors.toList ());

    }

    /**
     * 根据标签查找用户 SQL版本
     * @param tagNames
     * @return
     */
    @Deprecated  //表示方法作废
    private List<UserVO> searchUsersByTagBySQL(List<String> tagNames) {
        //拼接查询条件
        //SQL实现
        QueryWrapper<User> queryWrapper=new QueryWrapper<> ();
        for (String tagName : tagNames) {
            queryWrapper.like ("tag",tagName);
        }
        List<User> userList = userMapper.selectList (queryWrapper);
        return userList.stream ().map (user -> CopyUtils.copy (user,UserVO.class))
                .collect(Collectors.toList());
    }
}
